// -----------------------------
// Author: 		Dhaenens Gert	
// Creation: 	12/3/2014 8:10:30 PM				
// -----------------------------

#include "WaterMaterial.h"

#include "Application.h"
#include "FileManager.h"
#include "RenderManager.h"
#include "Timer.h"
#include "Scene.h"
#include "Camera.h"

#define MAX_GEOMETRY_OUTPUT_VERTICES	0x000FFFFF

struct GeometryOutputVertex
{
	Vector4	Position;
	Vector3	Normal;
};

struct MatrixBuffer
{
	Matrix World;
	Matrix WorldViewProj;
	Matrix View;
};

struct UvOffsetBuffer
{
	Vector2 OffsetR;
	Vector2 OffsetG;
	Vector2 OffsetB;
	Vector2 OffsetA;
	Float	WaterWidth;
	Float	WaterHeight;
	Vector2	Padding;
};

struct LightBuffer
{
	Vector3	Position;
	Float	Range;
	Vector3	Direction;
	Float	Intensity;
	UInt32	LightType;
	Vector3	Padding;
};

struct MaterialBuffer
{
	Vector3	CameraPosition;
	Float	Reflection;
	Vector3	DiffuseColor;
	Float	Specular;
	Vector3	SpecularColor;
	Float	SpecularPower;
	Vector3	AmbientColor;
	Float	Opacity;
	Float	AmbientStrength;
	Vector3	Padding;
};

// Tesselation Information
struct TesselationInfo
{
	Vector3	CameraPosition;
	Float	MinimumDistance;
	Float	MaximumDistance;
	Float	MaximumTessFactor;
	Vector2	Padding;
};

WaterMaterial::WaterMaterial(Application* lpApp)
	: ForwardMaterial(lpApp, true)
	, m_lpVertexShader(nullptr)
	, m_lpHullShader(nullptr)
	, m_lpTessInfoBuffer(nullptr)
	, m_lpDomainShader(nullptr)
	, m_lpGeometryShader(nullptr)
	, m_lpMatrixBuffer(nullptr)
	, m_lpUvOffsetBuffer(nullptr)
	, m_lpGeometryOutput(nullptr)
	, m_lpPixelShader(nullptr)
	, m_lpLightBuffer(nullptr)
	, m_lpMaterialBuffer(nullptr)
	, m_lpInputLayout(nullptr)
	, m_lpDisplacementMap(nullptr)
	, m_lpReflectionMap(nullptr)
	, m_lpTextureSampler(nullptr)
	, m_UvOffsetR(0.0f, 0.0f)
	, m_UvOffsetG(0.0f, 0.0f)
	, m_UvOffsetB(0.0f, 0.0f)
	, m_UvOffsetA(0.0f, 0.0f)
{
	Init(lpApp);
}

WaterMaterial::~WaterMaterial()
{
	NullRelease(&m_lpTextureSampler);
	NullRelease(&m_lpInputLayout);
	NullRelease(&m_lpMaterialBuffer);
	NullRelease(&m_lpLightBuffer);
	NullRelease(&m_lpPixelShader);
	NullRelease(&m_lpGeometryOutput);
	NullRelease(&m_lpUvOffsetBuffer);
	NullRelease(&m_lpMatrixBuffer);
	NullRelease(&m_lpGeometryShader);
	NullRelease(&m_lpDomainShader);
	NullRelease(&m_lpTessInfoBuffer);
	NullRelease(&m_lpHullShader);
	NullRelease(&m_lpVertexShader);
}

void WaterMaterial::Init(Application* lpApp)
{
	TString vertexShader = lpApp->GetFiles()->GetFileName(_T("Shader_WaterVertex"));
	TString hullShader = lpApp->GetFiles()->GetFileName(_T("Shader_WaterHull"));
	TString domainShader = lpApp->GetFiles()->GetFileName(_T("Shader_WaterDomain"));
	TString geometryShader = lpApp->GetFiles()->GetFileName(_T("Shader_WaterGeometry"));
	TString pixelShader = lpApp->GetFiles()->GetFileName(_T("Shader_WaterPixel"));

	m_lpVertexShader = ShaderCompiler::LoadVertexFromFile(m_lpRenderManager->GetDevice(), vertexShader.c_str());

	m_lpHullShader = ShaderCompiler::LoadHullFromFile(m_lpRenderManager->GetDevice(), hullShader.c_str());
	m_lpTessInfoBuffer = IBuffer::Create(m_lpRenderManager->GetDevice(), BufferType::Constant, Usage::Default, 0, sizeof(TesselationInfo), 1, nullptr);

	m_lpDomainShader = ShaderCompiler::LoadDomainFromFile(m_lpRenderManager->GetDevice(), domainShader.c_str());

	StreamOutputElement geomElements[] =
	{
		{ 0, "WORLDPOSITION", 0, 0, 4, 0 },
		{ 0, "NORMAL", 0, 0, 3, 1 },
	};
	UInt32 geomElementCount = ARRAYSIZE(geomElements);
	m_lpGeometryShader = ShaderCompiler::LoadGeometryStreamOutputFromFile(m_lpRenderManager->GetDevice(), geometryShader.c_str(), geomElements, geomElementCount);
	m_lpMatrixBuffer = IBuffer::Create(m_lpRenderManager->GetDevice(), BufferType::Constant, Usage::Default, 0, sizeof(MatrixBuffer), 1, nullptr);
	m_lpUvOffsetBuffer = IBuffer::Create(m_lpRenderManager->GetDevice(), BufferType::Constant, Usage::Default, 0, sizeof(UvOffsetBuffer), 1, nullptr);
	m_lpGeometryOutput = IBuffer::Create(m_lpRenderManager->GetDevice(), BufferType::StreamOutput, Usage::Default, 0, sizeof(GeometryOutputVertex), MAX_GEOMETRY_OUTPUT_VERTICES, nullptr);

	m_lpPixelShader = ShaderCompiler::LoadPixelFromFile(m_lpRenderManager->GetDevice(), pixelShader.c_str());
	m_lpLightBuffer = IBuffer::Create(m_lpRenderManager->GetDevice(), BufferType::Constant, Usage::Default, 0, sizeof(LightBuffer), 1, nullptr);
	m_lpMaterialBuffer = IBuffer::Create(m_lpRenderManager->GetDevice(), BufferType::Constant, Usage::Default, 0, sizeof(MaterialBuffer), 1, nullptr);

	m_lpInputLayout = IInputLayout::Create(m_lpRenderManager->GetDevice(), m_lpVertexShader);

	m_lpTextureSampler = ISamplerState::Create(m_lpRenderManager->GetDevice(), AddressMode::Mirror, AddressMode::Mirror, AddressMode::Mirror);
}

void WaterMaterial::Apply(Application* lpApp)
{
	IRenderDevice* lpDevice = m_lpRenderManager->GetDevice();

	// Bind Vertex
	lpDevice->SetVertexShader(m_lpVertexShader);

	// Bind Hull
	lpDevice->SetHullShader(m_lpHullShader);
	m_lpHullShader->SetBufferAsConstant(0, m_lpMatrixBuffer);
	m_lpHullShader->SetBufferAsConstant(1, m_lpTessInfoBuffer);
	m_lpHullShader->SetBufferAsConstant(2, m_lpUvOffsetBuffer);

	// Bind Domain
	lpDevice->SetDomainShader(m_lpDomainShader);

	// Bind Geometry
	lpDevice->SetGeometryShader(m_lpGeometryShader);
	m_lpGeometryShader->SetStreamOutput(m_lpGeometryOutput);
	m_lpGeometryShader->SetBufferAsConstant(0, m_lpMatrixBuffer);
	m_lpGeometryShader->SetBufferAsConstant(2, m_lpUvOffsetBuffer);
	if (m_lpDisplacementMap)
	{
		m_lpGeometryShader->SetTextureAsResource(0, m_lpDisplacementMap.Get());
		m_lpGeometryShader->SetSamplerState(0, m_lpTextureSampler);
	}

	// Bind Pixel
	lpDevice->SetPixelShader(m_lpPixelShader);
	m_lpPixelShader->SetBufferAsConstant(0, m_lpLightBuffer);
	m_lpPixelShader->SetBufferAsConstant(1, m_lpMaterialBuffer);
	if (m_lpReflectionMap)
	{
		m_lpPixelShader->SetTextureAsResource(0, m_lpReflectionMap.Get());
		m_lpPixelShader->SetSamplerState(0, m_lpTextureSampler);
	}

	// Bind InputManager & Topology
	lpDevice->SetInputLayout(m_lpInputLayout);
	lpDevice->SetTopology(Topology::Control4PointPatchList);
}
void WaterMaterial::UpdateMatrices(Application* lpApp)
{
	// Update the Matrix Buffer
	MatrixBuffer matrixBuffer = {};
	matrixBuffer.World = m_World;
	matrixBuffer.View = m_View;
	matrixBuffer.WorldViewProj = m_World * m_View * m_Projection;
	m_lpMatrixBuffer->Update(&matrixBuffer, sizeof(MatrixBuffer));
}
void WaterMaterial::UpdateData(Application* lpApp)
{
	// Update the Light Buffer
	LightBuffer lightBuffer = {};
	lightBuffer.Position = m_LightPosition;
	lightBuffer.Direction = m_LightDirection;
	lightBuffer.Range = m_LightRange;
	lightBuffer.Intensity = m_LightIntensity;
	m_lpLightBuffer->Update(&lightBuffer, sizeof(LightBuffer));

	// Calculate the UV Offsets
	Float deltaOffset = lpApp->GetTimer()->GetDeltaTime() * m_WaveSpeed;

	m_UvOffsetR.x += deltaOffset * 2.0f;
	m_UvOffsetG.y -= deltaOffset * 4.0f;
	m_UvOffsetB.x += deltaOffset * 8.0f;
	m_UvOffsetB.y -= deltaOffset * 8.0f;
	m_UvOffsetA.x += deltaOffset * 16.0f;
	m_UvOffsetA.y -= deltaOffset * 16.0f;

	// Update the UV Offset Buffer
	UvOffsetBuffer uvBuffer = {};
	uvBuffer.OffsetR = m_UvOffsetR;
	uvBuffer.OffsetG = m_UvOffsetG;
	uvBuffer.OffsetB = m_UvOffsetB;
	uvBuffer.OffsetA = m_UvOffsetA;
	uvBuffer.WaterWidth = m_WaterWidth;
	uvBuffer.WaterHeight = m_WaterHeight;
	m_lpUvOffsetBuffer->Update(&uvBuffer, sizeof(UvOffsetBuffer));

	// Update the Material Buffer
	MaterialBuffer matBuffer = {};
	matBuffer.CameraPosition = lpApp->GetScene()->GetCamera()->GetPosition();
	matBuffer.DiffuseColor = m_DiffuseColor.ToVector3();
	matBuffer.Reflection = m_Reflection;
	matBuffer.Specular = m_Specular;
	matBuffer.SpecularColor = m_SpecularColor.ToVector3();
	matBuffer.SpecularPower = m_SpecularPower;
	matBuffer.AmbientColor = m_AmbientColor.ToVector3();
	matBuffer.Opacity = m_Opacity;
	matBuffer.AmbientStrength = m_AmbientStrength;
	m_lpMaterialBuffer->Update(&matBuffer, sizeof(MaterialBuffer));

	// Update Tesselation Buffer
	TesselationInfo tessBuffer = {};
	tessBuffer.CameraPosition = lpApp->GetScene()->GetCamera()->GetPosition();
	tessBuffer.MinimumDistance = m_MinimumDistance;
	tessBuffer.MaximumDistance = m_MaximumDistance;
	tessBuffer.MaximumTessFactor = m_MaximumTessFactor;
	m_lpTessInfoBuffer->Update(&tessBuffer, sizeof(TesselationInfo));
}

void WaterMaterial::SetDisplacementTexture(RefPtr<ITexture2D> lpDisplacementTexture)
{
	m_lpDisplacementMap = lpDisplacementTexture;
}
void WaterMaterial::SetReflectionMap(RefPtr<ITexture2D> lpReflectionMap)
{
	m_lpReflectionMap = lpReflectionMap;
}

Bool WaterMaterial::IsInside(const Vector3& point)
{
	// Get the Vertices
	m_lpGeometryOutput->RecalculateCount();
	GeometryOutputVertex* lpVertices = reinterpret_cast<GeometryOutputVertex*>(m_lpGeometryOutput->GetData());
	UInt32 vertexCount = m_lpGeometryOutput->GetCount();

	delete[] lpVertices;

	return false;
}